#pragma once

#include "utility.h"
#include <mutex>

namespace qyc
{
	template<class T>
	class auto_ptr
	{
		typedef T* pointer;
		typedef auto_ptr<T> self;
	public:
		explicit auto_ptr(pointer ptr = nullptr)
			: _ptr(ptr)
		{}
		auto_ptr(self& right)
			: _ptr(right.release())
		{}
		auto_ptr(self&& right)
			: _ptr(right.release())
		{}
		self& operator=(self& right)
		{
			reset(right.release());
			return *this;
		}
		self& operator=(self&& right)
		{
			reset(right.release());
			return *this;
		}

		T& operator*() const
		{
			return *get();
		}
		pointer operator->() const
		{
			return get();
		}
		pointer get() const
		{
			return _ptr;
		}
		void reset(pointer ptr = nullptr)
		{
			if (_ptr)
				delete _ptr;
			_ptr = ptr;
		}
		pointer release()
		{
			pointer tmp = _ptr;
			_ptr = nullptr;
			return tmp;
		}
		void swap(self& right)
		{
			qyc::swap(_ptr, right._ptr);
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
				_ptr = nullptr;
			}
		}
	private:
		pointer _ptr = nullptr;
	};

	template<class T>
	struct default_delete
	{
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

	template<class T, class Del = default_delete<T>>
	class unique_ptr
	{
		typedef T* pointer;
		typedef unique_ptr<T, Del> self;
	public:
		explicit unique_ptr(pointer ptr = nullptr)
			: _ptr(ptr)
		{}
		unique_ptr(const self&) = delete;
		unique_ptr(self&& right)
		{
			swap(right);
		}
		self& operator=(const self&) = delete;
		self& operator=(self&& right)
		{
			swap(right);
			return *this;
		}
		explicit operator bool() const
		{
			return get();
		}

		T& operator*() const
		{
			return *get();
		}
		pointer operator->() const
		{
			return get();
		}
		pointer get() const
		{
			return _ptr;
		}
		void reset(pointer ptr = nullptr)
		{
			if (_ptr)
				Del()(_ptr);
			_ptr = ptr;
		}
		pointer release()
		{
			pointer tmp = _ptr;
			_ptr = nullptr;
			return tmp;
		}
		void swap(self& right)
		{
			qyc::swap(_ptr, right._ptr);
		}

		~unique_ptr()
		{
			if (_ptr)
				reset();
		}
	private:
		pointer _ptr = nullptr;
	};

	template<class Lock>
	class lock_guard
	{
		typedef lock_guard<Lock> self;
	public:
		lock_guard(Lock& lock)
			: _lock(lock)
		{
			_lock.lock();
		}
		lock_guard(const self&) = delete;
		self& operator=(const self&) = delete;

		~lock_guard()
		{
			_lock.unlock();
		}
	private:
		Lock& _lock;
	};

	template<class T, class Del = default_delete<T>>
	class shared_ptr
	{
		typedef T* pointer;
		typedef shared_ptr<T, Del> self;
	public:
		explicit shared_ptr(pointer ptr = nullptr)
			: _ptr(ptr)
			, _pcount(new size_t(1))
			, _pmtx(new std::mutex)
		{}
		shared_ptr(const self& right)
			: _ptr(right._ptr)
			, _pcount(right._pcount)
			, _pmtx(right._pmtx)
		{
			add_refcount();
		}
		shared_ptr(self&& right)
			: _ptr(nullptr)
			, _pcount(new size_t(1))
			, _pmtx(new std::mutex)
		{
			swap(*this, right);
		}
		self& operator=(const self& right)
		{
			if (&right != this)
			{
				release();
				_ptr = right._ptr;
				_pcount = right._pcount;
				_pmtx = right._pmtx;
				add_refcount();
			}
			return *this;
		}
		self& operator=(self&& right)
		{
			swap(*this, right);
			return *this;
		}

		T& operator*() const
		{
			return *get();
		}
		pointer operator->() const
		{
			return get();
		}
		pointer get() const
		{
			return _ptr;
		}
		size_t use_count() const
		{
			return *_pcount;
		}
		void reset(pointer ptr = nullptr)
		{
			release();
			_ptr = ptr;
			_pcount = new size_t(1);
		}
		void swap(self& right)
		{
			qyc::swap(_ptr, right._ptr);
			qyc::swap(_pcount, right._pcount);
			qyc::swap(_pmtx, right._pmtx);
		}

		~shared_ptr()
		{
			release();
		}
	private:
		void add_refcount()
		{
			lock_guard<std::mutex> lg(*_pmtx);
			++(*_pcount);
		}
		void release()
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				Del()(_ptr);
				delete _pcount;
				flag = true;
			}
			_pmtx->unlock();
			if (flag)
				delete _pmtx;
			_pmtx = nullptr;
			_ptr = nullptr;
			_pcount = nullptr;
		}

		pointer _ptr = nullptr;
		size_t* _pcount = nullptr;
		std::mutex* _pmtx = nullptr;
	};

	template<class T>
	class weak_ptr
	{
		typedef T* pointer;
		typedef weak_ptr<T> self;
		typedef shared_ptr<T> shared_ptr;
	public:
		weak_ptr() = default;
		weak_ptr(const self&) = default;
		weak_ptr(self&&) = default;
		weak_ptr(const shared_ptr& shareptr)
			: _ptr(shareptr.get())
		{}
		self& operator=(const self&) = default;
		self& operator=(self&&) = default;
		self& operator=(const shared_ptr& shareptr)
		{
			_ptr = shareptr.get();
			return *this;
		}

		T& operator*() const
		{
			return *get();
		}
		pointer operator->() const
		{
			return get();
		}
		pointer get() const
		{
			return _ptr;
		}
		void swap(self& right)
		{
			qyc::swap(_ptr, right._ptr);
		}

		~weak_ptr()
		{
			_ptr = nullptr;
		}
	private:
		pointer _ptr = nullptr;
	};
}